iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
自我挑戰組

laravel+vue 學習系列 第 12

Day12. 容器 Container

  • 分享至 

  • xImage
  •  

一、依賴注入(DI)與控制反轉(IoC)

  1. 一般 Class 要使用非自身的物件時, 需在使用時 new 一個物件

    • 範例 Class APP\Models\Product 需要使用到 APP\Models\Category 物件取得商品分類資訊
    • 依賴注入:
      • Product 依賴 Category
      • Product 在自己的 construct 內 new 一個 Category
    • 無控制反轉: 因為取得物件是由 Product 本身控制
    • 建立一個商品 Class
        # class-product.php
        namespace App\Models;
        use App\Models\Category;
    
        Class Product {
            public $category;
            function __construct() {
                echo 'Class Product 引入了';
                # 在使用時 new 一個 Category
                $this->category = new Category();
            }
    
            public function show() {
                echo '顯示 Product';
                echo ' 同時' ;
                $this->category->show();
            }
        }
    
    • 建立商品類別 Class
        namespace App\Models;
    
        Class Category {
            function __construct() {
                echo "Category Class 引入了";
            }
    
            public function show() {
                echo '顯示 Category';
            }
        }
    
    • 使用 Product
        $p = new Product();
        $p->show();
    

    https://ithelp.ithome.com.tw/upload/images/20220917/20128127BK2wzLFeXu.png

  2. 使用參數的方式引用需要的類別

    • 範例 Class APP\Models\DI\Product 需要使用到 APP\Models\DI\Category 物件取得商品分類資訊
    • 依賴注入
      • Product 依賴 Category
      • Product new 時加入參數注入 Category
    • 控制反轉:
      • Product 所需要的 Categroy 由外部使用者在建立 Product 同時指定 Category
    • 建立一個商品 Class
        namespace App\Models\DI;
    
        Class Product {
            public $category;
            function __construct(Category $category) {
    
                echo 'Class Product DI 引入了';
                $this->category = $category;
    
            }
    
            public function show() {
    
                echo '顯示 Product DI';
                echo ' 同時' ;
                $this->category->show();
    
            }
        }
    
    • 建立商品類別 Class
        namespace App\Models\DI;
    
        Class Category {
            function __construct() {
                echo "Category DI Class 引入了";
            }
    
            public function show() {
                echo '顯示 Category DI';
            }
        }
    
    • 使用 Product
        # 使用者自行 new 一個 Category
        $category = new Category(); 
    
        # 在 new Product 時隨參數傳入 Product 內
        $p2 = new Product($category);
        $p2->show();
    

    https://ithelp.ithome.com.tw/upload/images/20220917/20128127vRpwv9tl5W.png

  3. 使用 PHP Relfection 製作一個容器, 實現依賴注入(DI)與控制反轉(IoC)

    • 建立一個 Container 利用 PHP Relfection 自動連接(autowiring) 需要的 Class
    • 依賴注入
      • Product 依賴 Category
      • Product new 時加入參數注入 Category
    • 控制反轉:
      • Product 所需要的 Categroy 由 Container 判斷 Class 參數 typehint 的類別, 使用 PHP Relfection 注入
    • 建立一個容器 Container
        namespace App\Kernel;
    
        Class Container {
    
            public static function autoClassRelfection($className, $params = []) {
                $reflect = new \ReflectionClass($className);
    
                // 取得構造函數
                $construct = $reflect->getConstructor();
    
                $args = [];
                if ( $construct ) {
                    // 取得建構子的參數
                    $construct_params = $construct->getParameters();
    
                    foreach( $construct_params as $param ) {
                        $class = $param->getClass();
    
                        if ( $class ) {
                            $args[] = self::autoClassRelfection($class->name);
                        }
                    }
                }
                $args = array_merge($args, $params);                
    
                $instance = $reflect->newInstanceArgs($args);
    
                return $instance;
            }
        }
    
    • 使用 Prodcut (Product 與 Category 使用上方建立的)
        $instance_product = Container::autoClassRelfection(Product::class);
        $instance_product->show();
    

    https://ithelp.ithome.com.tw/upload/images/20220917/20128127yr6f7W31nH.png

    參考:
    https://learnku.com/articles/56111
    https://hackmd.io/@Daniel-Handsome/HJfzxORyF#%E6%87%89%E7%94%A8
    GitHub 實作
    https://github.com/harrison9824003/PHP

二、Laravel 容器

  1. 主要解析與綁定類別和介面的具體實例

  2. 依賴注入項目

    • 由外面注入依賴類別
      a. 建構式注入
      b. setter 注入
      c. 方法注入
    • 優點: 可自由改變要注入的項目
    • 缺點: 當有很多依賴注入時, 會顯得很雜亂
  3. app() 全域輔助函式(app() 為 Laravel 容器實例)

    • 參數為類別名稱, 可有兩種方式
      • FQCN
          $logger = app('APP\ProductController');
      
      • 靜態 class
          $logger = app(Logger::class);
      
    • 容器使用自動連接(autowiring)方法, 根據所提供的 typehint 參數來解析需要的類別, 開發人員不需要另外綁定 Class
  4. Laravel 綁定方式

    • Closure
        # 設定在 Providider         
        public function register() {
            # 開發者使用 app(Logger::class) 時就會回傳 Closure
            $this->app->bind(Logger:class, function($app) {
                return new Logger('log\path', 'error'); 
            });
        }
    
    • 綁定 singleton、別名與實例

      • 將綁定的 Closure 輸出存入快取, 在每次呼叫時不用重新建立一個新的 Closure
          public function register() {            
              $this->app->singleton(Logger:class, function($app) {
                  return new Logger('log\path', 'error'); 
              });
          } 
      
      • 將實例綁定到容器, 作用與 singleton 類似
          public function register() {            
              $logger = new Logger('log\path', 'error');
              $this->app->instance(Logger::class, $logger);
          } 
      
      • 取別名或使用字串
          # 1. 要求 Logger, 提供 CustomLog
          $this->app->bind(Logger::class, CustomLog::class);
      
          # 2. 要求 log, 提供CustomLog
          $this->app->bind('log', CustomLog::class);
      
          #3. 要求 log, 提供CustomLog
          $this->app->alias(CustomLog::class, 'log');
      
      • 將具體實例綁定介面
          # 建構式綁定 interface Class, 這樣可以自由使用繼承他的子類別
          class Logger {
              protected $mailer;
      
              public function __construct(MailerInterface $mailer){
      
              }
          }
      
          # 服務提供
          # 假設 Mailer 與 MailerCustom 都是繼承 MailerInterface
          public function register() {
              # 此時可以使用 MailerSelf 或 MailerCustom 來發送郵件
              # 只需要在 Provider 內調整
              $this->app->bind(\Interfaces\Mailer::class, function() {
                  # return new MailerSelf();
                  return new MailerCustom();
              }
          }
      
      • 情境綁定
          # 依照指定條件給予相對的 Class
          public function register() {
              $this->app->when(Jobs\SendErrorEmail::class)
              ->nees(Interfaces\Mailer::class)
              ->give(Mailer\Self::class);
          }
      

三、靜態介面與容器

  1. 目的是要讓開發人員可以直接利用靜態方法呼叫非靜態資源
    # 靜態呼叫
    Log::alert('test');
    
    # 等同於使用容器
    $logger = app('log');
    $logger->alert('test');
  1. 創建靜態介面
    • 將要使用的 Class 綁定到容器上
        namespace Shop;
        class Product {
            public function editProduct() {
                // ... 要執行的內容
            }
        }
    
        # 綁定到容器上
        App::bind('editproduct', function(){
            return new \Shop\Product;
        }
    
    • 建立 Facade Class
        # 繼承 Illuminate\Support\Facades\Facade;
        use Illuminate\Support\Facades\Facade;
    
        # 定義 getFacadeAccessor() 方法
        class Payment extend Facade {
            protected static function getFacadeAccessor() {
                # 回傳剛綁定在容器上的 Class
                return 'editproduct';
            }
        }
    
    
    
    • 將 Facade 綁定到 config/app.php aliase 陣列裡
        # 實際運用
        Product::editProduct();
        # 或是
        app('editproduct')->editProduct();
    
  2. laravel 預設靜態
    https://laravel.com/docs/5.0/facades#facade-class-reference

上一篇
Day11. 測試
下一篇
Day13. Laravel 資料庫與 Eloquent 之一
系列文
laravel+vue 學習32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言